package org.unidata.mdm.rest.v1.meta.service.entities;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import org.unidata.mdm.meta.context.GetDataModelContext;
import org.unidata.mdm.meta.context.UpsertDataModelContext;
import org.unidata.mdm.meta.dto.GetModelDTO;
import org.unidata.mdm.meta.dto.GetNestedEntitiesResult;
import org.unidata.mdm.rest.v1.meta.converter.entities.NestedEntityConverter;
import org.unidata.mdm.rest.v1.meta.exception.MetaRestExceptionIds;
import org.unidata.mdm.rest.v1.meta.ro.entities.NestedEntityRO;
import org.unidata.mdm.rest.v1.meta.ro.entities.nested.DeleteNestedEntityResultRO;
import org.unidata.mdm.rest.v1.meta.ro.entities.nested.GetNestedEntitiesResultRO;
import org.unidata.mdm.rest.v1.meta.ro.entities.nested.GetNestedEntityResultRO;
import org.unidata.mdm.rest.v1.meta.ro.entities.nested.UpsertNestedEntityRequestRO;
import org.unidata.mdm.rest.v1.meta.ro.entities.nested.UpsertNestedEntityResultRO;
import org.unidata.mdm.system.exception.PlatformBusinessException;

/**
 * Nested entity rest controller
 *
 * @author Alexandr Serov
 * @since 24.11.2020
 **/
@Path(NestedEntityRestService.SERVICE_PATH)
@Consumes({"application/json"})
@Produces({"application/json"})
public class NestedEntityRestService extends AbstractMetaEntitiesRestService {

    public static final String SERVICE_PATH = "nested-entities";

    public static final String SERVICE_TAG = "nested-entities";

    private static final String NOT_FOUND_ERROR = "Nested entity %s not found (draftId: %s)";

    @GET
    @Path("{id}")
    @Operation(description = "Gets a register by ID.", method = HttpMethod.GET, tags = SERVICE_TAG)
    public GetNestedEntityResultRO findByName(@Parameter(description = "ID.", in = ParameterIn.PATH)
                                              @PathParam("id") String id,
                                              @Parameter(description = "Draft ID. Optional.", in = ParameterIn.QUERY)
                                              @QueryParam("draftId") @DefaultValue("0") Long draftId,
                                              @Parameter(description = "Check for data existance.", in = ParameterIn.QUERY)
                                              @QueryParam("checkData") @DefaultValue("true") Boolean checkData) {
        GetNestedEntityResultRO result = new GetNestedEntityResultRO();
        result.setNestedEntity(NestedEntityConverter.to(nestedByName(id, draftId).getNested()));
        return result;
    }

    /**
     * Gets a list of register entities.
     *
     * @param draftId draftId
     * @return list of entity info
     */
    @GET
    @Operation(
        description = "Register list.",
        method = HttpMethod.GET,
        tags = SERVICE_TAG)
    public GetNestedEntitiesResultRO findAll(@Parameter(description = "Draft id. Optional.", in = ParameterIn.QUERY)
                                             @QueryParam("draftId") @DefaultValue("0") Long draftId) {
        GetNestedEntitiesResultRO result = new GetNestedEntitiesResultRO();
        List<GetNestedEntitiesResult> nesteds = findEntitiesByDraft(draftId, GetModelDTO::getNested);
        if (!nesteds.isEmpty()) {
            result.setNestedEntities(nesteds.stream()
                .map(GetNestedEntitiesResult::getNested)
                .map(NestedEntityConverter::to)
                .collect(Collectors.toList()));
        } else {
            result.setNestedEntities(Collections.emptyList());
        }
        return result;
    }


    @POST
    @Path("upsert")
    @Operation(
        description = "Create or update a register.",
        method = HttpMethod.PUT,
        requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = UpsertNestedEntityRequestRO.class)), description = "Upsert request."),
        tags = SERVICE_TAG
    )
    public UpsertNestedEntityResultRO upsert(UpsertNestedEntityRequestRO req) {
        Objects.requireNonNull(req, "Request can't be null");
        UpsertNestedEntityResultRO result = new UpsertNestedEntityResultRO();
        NestedEntityRO src = notNull("nestedEntity", req.getNestedEntity());
        Long draftId = resolveDraftId(req.getDraftId());
        metaModelService.upsert(UpsertDataModelContext.builder()
            .nestedEntitiesUpdate(NestedEntityConverter.from(src))
            .draftId(draftId)
            .build());
        req.setDraftId(draftId);
        result.setDraftId(draftId);
        return result;
    }

    /**
     * Delete lookup entity.
     *
     * @param id id to delete
     * @return 200 Ok
     */
    @DELETE
    @Path("{id}")
    @Operation(
        description = "Removes a register.",
        method = HttpMethod.DELETE,
        parameters = {
            @Parameter(description = "Register id.", in = ParameterIn.PATH, name = "id"),
            @Parameter(description = "Existing draft id. Optional.", in = ParameterIn.QUERY, name = "draftId")
        }, tags = SERVICE_TAG
    )
    public DeleteNestedEntityResultRO delete(@PathParam("id") String id, @QueryParam("draftId") @DefaultValue("0") Long draftId) {
        DeleteNestedEntityResultRO result = new DeleteNestedEntityResultRO();
        GetNestedEntitiesResult dto = nestedByName(id, draftId);
        metaModelService.upsert(UpsertDataModelContext.builder()
            .nestedEntitiesDelete(id)
            .draftId(resolveDraftId(draftId))
            .build());
        result.setId(id);
        return result;
    }

    private GetNestedEntitiesResult nestedByName(String entityName, Long draftId) {
        GetNestedEntitiesResult result = findNestedByName(entityName, draftId);
        if (result == null) {
            throw new PlatformBusinessException(String.format(NOT_FOUND_ERROR, entityName, draftId), MetaRestExceptionIds.EX_META_DATA_ENTITY_NOT_FOUND);
        }
        return result;
    }

    private <T> List<T> findEntitiesByDraft(Long draftId, Function<GetModelDTO, List<T>> mapper) {
        return findEntitiesByRequest(GetDataModelContext.builder()
                .draftId(resolveDraftId(draftId))
                .allNestedEntities(true)
                .build(), mapper);
    }

    private GetNestedEntitiesResult findNestedByName(String entityName, Long draftId) {
        return findFirstEntityByRequest(GetDataModelContext.builder()
            .nestedEntityIds(Collections.singletonList(entityName))
            .draftId(resolveDraftId(draftId))
            .build(), GetModelDTO::getNested);
    }
}
